/*
  Copyright (C) 2011, Kyungjoo Kim
  
  This file is part of LINAL (LINear ALgebra)
  
  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
  
  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  Also add information on how to contact you by electronic and paper mail.
  
  
  Kyungjoo Kim
  iamkyungjoo@gmail.com
*/
#ifndef LINAL_UTIL_HXX
#define LINAL_UTIL_HXX

namespace linal {

  // ----------------------------------------------------------------
  /*! Command line options
   */

  extern void initialize(int argc, char **argv);
  extern void finalize();
  extern void verbose_begin();
  extern void verbose_end();
  extern bool get_verbose();

  extern void get_option_bool(const char *opt, const char *text, const char *man, 
                              bool def, bool &val, int mode=LINAL_OPTION_MODE_RUN);
  extern void get_option_int (const char *opt, const char *text, const char *man, 
                              int def, int &val, int mode=LINAL_OPTION_MODE_RUN);
  extern void get_option_double(const char *opt,const char *text,const char *man,
                                double def, double &val, int mode=LINAL_OPTION_MODE_RUN);

  // ----------------------------------------------------------------
  /*! OpenMP lock
   */
  extern void init_locks            (int n_locks);
  extern void destroy_locks         ();
  extern void set_lock              (int i);
  extern void unset_lock            (int i);
  extern int  test_lock             (int i);

  // ----------------------------------------------------------------
  /*! FLOP counting - From LAPACK working note #41
   */
  extern double get_flop_gemm       ( int is_complex, int m, int n, int k );
  extern double get_flop_syrk       ( int is_complex, int k, int n );
  extern double get_flop_trsm_lower ( int is_complex, int m, int n );
  extern double get_flop_trsm_upper ( int is_complex, int m, int n );
  extern double get_flop_trmm_lower ( int is_complex, int m, int n );
  extern double get_flop_trmm_upper ( int is_complex, int m, int n );
  extern double get_flop_lu         ( int is_complex, int m, int n );
  extern double get_flop_chol       ( int is_complex, int n );
  extern double get_flop_ldl        ( int is_complex, int nn );
  extern double get_flop_qr         ( int is_complex, int m, int n );
  extern double get_flop_q          ( int is_complex, int m, int n, int k );
  
  extern double get_memory          ( int is_complex, int m, int n );

  // ----------------------------------------------------------------
  /*! Error handler
   */


#define LINAL_ERROR(eval, msg)						\
  linal::error((eval),__FILE__,__LINE__,"[LINAL ERROR] ", "%s\n", msg);

#define LINAL_ERROR_DETAIL(eval,format,...)				\
  linal::error((eval), __FILE__,__LINE__,"[LINAL ERROR] ", format, __VA_ARGS__);

  //#define LINAL_ERROR_DRYRUN
  inline void error(const int eval,
		    const char *file, const int line,
		    const char *prefix, 
		    const char *format,
		    ...) {
#ifdef LINAL_ERROR_DRYRUN
    if (eval) {
      char buffer[1024];
      va_list args;
      va_start( args, format );
      vsprintf( buffer, format, args );
      fprintf(stderr, "%s %s, %d\n", prefix, file, line);
      fprintf(stderr, "What to check %s\n", buffer);
    }
#else
    if (!eval) {
      char buffer[1024];
      va_list args;
      va_start( args, format );
      vsprintf( buffer, format, args );
      fprintf(stderr, "%s %s, %d\n", prefix, file, line);
      throw std::logic_error( buffer );
    }
#endif    
  }

  // ----------------------------------------------------------------
  /*! Check util
   */

  inline bool exist(FLA_Obj &A) { return (A.base != NULL); }
  inline bool exist(FLA_Obj &A, FLA_Obj &B) { 
    return (A.base != NULL && B.base != NULL);
  }
  inline bool exist(FLA_Obj &A, FLA_Obj &B, FLA_Obj &C) {
    return (A.base != NULL && B.base != NULL && C.base != NULL);
  }

  inline bool check_all_elemtype(int elem) {
    return ( elem == LINAL_MATRIX ||
             elem == LINAL_SCALAR );
  }

  inline bool check_all_scalar_type(int type) {
    return ( type == LINAL_INT ||
             type == LINAL_SINGLE_REAL ||
             type == LINAL_DOUBLE_REAL ||
             type == LINAL_SINGLE_COMPLEX ||
             type == LINAL_DOUBLE_COMPLEX );
  }

  inline bool check_double_scalr_type(int type) {
    return ( type == LINAL_INT ||
             type == LINAL_DOUBLE_REAL ||
             type == LINAL_DOUBLE_COMPLEX );
  }

  inline bool check_single_scalar_type(int type) {
    return ( type == LINAL_INT ||
             type == LINAL_SINGLE_REAL ||
             type == LINAL_SINGLE_COMPLEX );
  }
  
  inline bool check_side(int side) {
    return ( side == LINAL_LEFT || 
             side == LINAL_RIGHT );
  }

  inline bool check_diag(int diag) {
    return ( diag == LINAL_ZERO_DIAG || 
             diag == LINAL_NONUNIT_DIAG || 
             diag == LINAL_UNIT_DIAG );
  }

  inline bool check_trans_only(int trans) {
    return ( trans == LINAL_TRANSPOSE ||
             trans == LINAL_NO_TRANSPOSE );
  }

  inline bool check_conj_trans_only(int trans) {
    return ( trans == LINAL_CONJ_TRANSPOSE ||
             trans == LINAL_CONJ_NO_TRANSPOSE );
  }
  
  inline bool check_trans_all(int trans) {
    return ( trans == LINAL_TRANSPOSE ||
             trans == LINAL_NO_TRANSPOSE ||
             trans == LINAL_CONJ_TRANSPOSE ||
             trans == LINAL_CONJ_NO_TRANSPOSE );
  }

  inline bool check_uplo(int uplo) {
    return ( uplo == LINAL_LOWER_TRIANGULAR ||
             uplo == LINAL_UPPER_TRIANGULAR );
  }

  inline bool check_complex_index(int index) {
    return ( index == LINAL_COMPLEX_REAL_PART ||
             index == LINAL_COMPLEX_IMAG_PART );
  }

  inline int get_datatype_real(int datatype) {
    int r_val;
    switch (datatype) {
    case FLA_FLOAT:
    case FLA_COMPLEX:        r_val = FLA_FLOAT;  break;
    case FLA_DOUBLE:
    case FLA_DOUBLE_COMPLEX: r_val = FLA_DOUBLE; break;
    case FLA_INT:            r_val = FLA_INT;    break;
    }
    return r_val;
  }
  
}

#endif
